package com.hyt.it.ogt.kq.service.bm.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.IdcardUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.google.common.collect.Lists;
import com.hyt.check.CheckUtil;
import com.hyt.check.ruler.summary.IdCardRuler;
import com.hyt.check.ruler.summary.IntRuler;
import com.hyt.check.ruler.summary.StrRuler;
import com.hyt.common.apiInfor.ApiResponse;
import com.hyt.core.annotation.FailMapping;
import com.hyt.core.annotation.SuccessMapping;
import com.hyt.core.base.BaseCheckEntity;
import com.hyt.core.base.BaseEntity;
import com.hyt.core.base.BaseServiceImpl;
import com.hyt.core.constant.CommonConstant;
import com.hyt.core.enums.DelFlagEnum;
import com.hyt.core.enums.NumberEnum;
import com.hyt.core.exception.CheckException;
import com.hyt.core.util.FastJsonUtil;
import com.hyt.core.util.RemoteUtil;
import com.hyt.core.util.TableCheckViewUtil;
import com.hyt.core.util.UUIDUtils;
import com.hyt.enumeration.IdCardTypeEnum;
import com.hyt.exception.exception.BusinessLogicException;
import com.hyt.it.ogt.cj.enums.ComponentNameEnum;
import com.hyt.it.ogt.cj.service.IProjectCollectInfoService;
import com.hyt.it.ogt.kq.common.bm.utils.Constant;
import com.hyt.it.ogt.kq.common.config.ConfigManager;
import com.hyt.it.ogt.kq.common.utils.KqStringUtils;
import com.hyt.it.ogt.kq.service.bm.den.ExcelExport;
import com.hyt.it.ogt.kq.service.bm.feign.sq.SqClient;
import com.hyt.it.ogt.kq.service.bm.feign.sq.model.OrganCandidateInfoVO;
import com.hyt.it.ogt.kq.service.bm.feign.sq.model.PtCandidateParamVO;
import com.hyt.it.ogt.kq.service.bm.mapper.NonOpenMapper;
import com.hyt.it.ogt.kq.service.bm.mapper.ProjectMapper;
import com.hyt.it.ogt.kq.service.bm.model.entity.NonOpenInfo;
import com.hyt.it.ogt.kq.service.bm.model.entity.Project;
import com.hyt.it.ogt.kq.service.bm.model.entity.ProjectSubject;
import com.hyt.it.ogt.kq.service.bm.model.entity.StudentInfo;
import com.hyt.it.ogt.kq.service.bm.model.param.NonOpenInfoSaveParam;
import com.hyt.it.ogt.kq.service.bm.model.vo.*;
import com.hyt.it.ogt.kq.service.bm.service.INonOpenService;
import com.hyt.it.ogt.kq.service.bm.service.IProjectService;
import com.hyt.it.ogt.kq.service.bm.service.IProjectSubjectService;
import com.hyt.it.ogt.kq.service.gov.model.dto.ImportStudentQualificationsDTO;
import com.hyt.it.ogt.kq.service.gov.model.dto.ProgressResult;
import com.hyt.it.ogt.kq.service.gov.model.excel.ExportTaskCandidateSheet;
import com.hyt.it.ogt.kq.service.gov.model.excel.handler.CustomCellWriteHandler;
import com.hyt.it.ogt.kq.service.gov.model.param.*;
import com.hyt.it.ogt.kq.service.gov.model.vo.*;
import com.hyt.it.ogt.kq.service.gov.service.obs.CloudObsService;
import com.hyt.loginfo.model.Organization;
import com.hyt.loginfo.remote.PermissionLoginClient;
import com.hyt.loginfo.service.IBaseLoginService;
import com.hyt.loginfo.service.IUaBusinessAccessService;
import com.hyt.loginfo.util.OfficeIdToNameUtil;
import com.hyt.loginfo.util.UserIdToNameUtil;
import com.hyt.model.PageParam;
import com.hyt.progress.service.ProgressManager;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class INonOpenServiceImpl extends BaseServiceImpl<NonOpenMapper, NonOpenInfo> implements INonOpenService {

    @Resource
    private ConfigManager configManager;
    @Resource
    private ExcelExport excelExport;

    @Resource
    private ProjectMapper projectMapper;

    @Resource
    private NonOpenMapper nonOpenMapper;

    @Resource
    private IBaseLoginService iLoginService;
    @Resource
    IProjectSubjectService iProjectSubjectService;
    @Resource
    IProjectService iProjectService;
    @Resource
    protected UserIdToNameUtil<CheckOrderList> userIdToNameUtil;
    @Resource
    IUaBusinessAccessService iUaBusinessAccessService;
    @Resource
    protected OfficeIdToNameUtil<NonOpenInfo> officeIdToNameUtil;
    @Resource
    IProjectCollectInfoService iProjectCollectInfoService;
    @Resource
    SqClient sqClient;
    @Resource
    PermissionLoginClient permissionLoginClient;
    @Resource
    private CloudObsService cloudObsService;

    @Override
    public IPage<NonOpenInfoVO> queryPage(NoOpenParam projectParam) {
        // 如果没有机构采集信息则不设置机构权限查询
        Boolean hasOfficePermission = iProjectCollectInfoService.isComponentRequired(projectParam.getProjectId(), ComponentNameEnum.office.getCode());
        if (!hasOfficePermission) {
            projectParam.setDeptIds(null);
        }
        IPage<NonOpenInfo> page = nonOpenMapper.queryPage(projectParam);
        officeIdToNameUtil.change(page.getRecords(), NonOpenInfo::getOfficeId, NonOpenInfo::setOfficeName);
        officeIdToNameUtil.change(page.getRecords(), NonOpenInfo::getDeptId, NonOpenInfo::setOfficeName);
        IPage<NonOpenInfoVO> convert = page.convert(nonOpenInfo -> {
            NonOpenInfoVO nonOpenInfoVO = BeanUtil.toBean(nonOpenInfo, NonOpenInfoVO.class);
            // 设置科目名称集合
            String subjectNames = Opt.ofNullable(nonOpenInfo.getSubjectIds())
                    .map(data -> StrUtil.split(nonOpenInfo.getSubjectIds(), StrPool.COMMA))
                    .stream()
                    .flatMap(Collection::stream)
                    .filter(StrUtil::isNotBlank)
                    .map(subjectId -> iProjectSubjectService.queryBySubjectId(projectParam.getProjectId(), subjectId))
                    .map(ProjectSubject::getSubjectName)
                    .filter(StrUtil::isNotBlank)
                    .distinct()
                    .collect(Collectors.joining(DOT));
            nonOpenInfoVO.setSubjectNames(subjectNames);
            return nonOpenInfoVO;
        });
        return convert;
    }

    public void exportNonOpenInfo(NoOpenQueryParam projectParam) throws Exception {
        Boolean hasOfficePermission = iProjectCollectInfoService.isComponentRequired(projectParam.getProjectId(), ComponentNameEnum.office.getCode());
        if (!hasOfficePermission) {
            projectParam.setDeptIds(null);
        }

        String lastFileName = UUIDUtils.randomUUID() + ".xlsx";
        File tempFile = FileUtil.createTempFile(lastFileName, Boolean.TRUE);
        tempFile.deleteOnExit();

        List<NonOpenInfo> nonOpenInfoList = nonOpenMapper.queryList(projectParam);
        officeIdToNameUtil.change(nonOpenInfoList, NonOpenInfo::getOfficeId, NonOpenInfo::setOfficeName);
        officeIdToNameUtil.change(nonOpenInfoList, NonOpenInfo::getDeptId, NonOpenInfo::setOfficeName);
        List<NonOpenInfoExportVo> resultList = nonOpenInfoList.stream().map(nonOpenInfo -> {
            NonOpenInfoExportVo nonOpenInfoVO = BeanUtil.toBean(nonOpenInfo, NonOpenInfoExportVo.class);
            // 设置科目名称集合
            if(nonOpenInfoVO.getIsBm()){
                nonOpenInfoVO.setIsBmName("已报名");
            }else{
                nonOpenInfoVO.setIsBmName("未报名");
            }
            IdCardTypeEnum idCardTypeEnum = IdCardTypeEnum.getByKeyword(nonOpenInfoVO.getCardType());
            nonOpenInfoVO.setCardType(idCardTypeEnum.getIdCardTypeName());
            String subjectNames = Opt.ofNullable(nonOpenInfo.getSubjectIds())
                    .map(data -> StrUtil.split(nonOpenInfo.getSubjectIds(), StrPool.COMMA))
                    .stream()
                    .flatMap(Collection::stream)
                    .filter(StrUtil::isNotBlank)
                    .map(subjectId -> iProjectSubjectService.queryBySubjectId(projectParam.getProjectId(), subjectId))
                    .map(ProjectSubject::getSubjectName)
                    .filter(StrUtil::isNotBlank)
                    .distinct()
                    .collect(Collectors.joining(DOT));
            nonOpenInfoVO.setSubjectNames(subjectNames);
            return nonOpenInfoVO;
        }).collect(Collectors.toList());

// 导出Excel
        ProgressManager.updateProgressCurrent(42L, "数据准备中");

        ProgressManager.updateProgressCurrent(55, "数据准备中");
        //考生
        EasyExcel.write(tempFile, NonOpenInfoExportVo.class).sheet("报名资格信息表").doWrite(resultList);

        String path = "bm/nonOpen/" + lastFileName;
        ProgressManager.updateProgressCurrent(81L, "文件开始生成");
        String fileUrl = cloudObsService.updateFile(path, tempFile);
        log.info("# excel文件上传华为云成功：{}，{}", lastFileName, fileUrl);
        if(StrUtil.isNotBlank(fileUrl)){
            ProgressManager.finish(ProgressResult.builder().success(Boolean.TRUE).result(fileUrl).build());
        }else{
            ProgressManager.finish(ProgressResult.builder().success(Boolean.FALSE).result("导出失败").build());
        }

    }

    @Override
    public void deleteStudent(String id) {
        NonOpenInfo nonOpenInfo = getById(id);
        if (nonOpenInfo == null) {
            return;
        }
        if (nonOpenInfo.getIsBm()) {
            BusinessLogicException.throwException(31211207, "当前考生已报名", nonOpenInfo);
        }

        this.lambdaUpdate()
                .eq(BaseEntity::getId, id)
                .set(BaseEntity::getDelFlag, Boolean.TRUE)
                .set(BaseEntity::getUpdateBy, iLoginService.getUserId())
                .set(BaseEntity::getUpdateDate, LocalDateTime.now())
                .update();
    }

    @Override
    public int statQualifications(String projectId, Set<String> deptIds) {
        return nonOpenMapper.statQualifications(projectId, deptIds);
    }

    @Override
    public void downStudentDemo(HttpServletResponse response, String projectId) {
        Project project = projectMapper.selectById(projectId);
        String title = project.getProjectName() + "_考生导入模板";
        excelExport.exporExcelDemo(response, title, null);
    }

    @Transactional(rollbackFor = Exception.class)
    @SneakyThrows
    @Override
    public void importStudentQualificationsData(ImportStudentQualificationsDataParam param) {
        Organization organization = iLoginService.getDeptInfo();

        for (int i = 0; i < param.getStudentQualifications().size(); i++) {
            ImportStudentQualificationVO studentDatum = CollUtil.get(param.getStudentQualifications(), i);
            boolean nonExistIdCard = filterNonExistIdCard(studentDatum.getCardNumber(), param.getProjectId());
            if (nonExistIdCard) {
                String format = StrUtil.format("{}考生数据校验通过", studentDatum.getStudentName());
                ProgressManager.stepProgressCurrent(i, param.getStudentQualifications().size(), 30, 30, format);
                continue;
            }
            BusinessLogicException.throwException(31213202, "考生信息已存在库中", param);
        }

        List<NonOpenInfo> openInfoList = param.getStudentQualifications().stream()
                .map(data -> {
                    NonOpenInfo nonOpenInfo = BeanUtil.toBean(data, NonOpenInfo.class);
                    nonOpenInfo.setId(UUIDUtils.newSortUUID());
                    nonOpenInfo.setProjectId(param.getProjectId());
                    nonOpenInfo.setOfficeId(param.getOfficeId());
                    nonOpenInfo.setDeptId(param.getDeptId());
                    nonOpenInfo.setAncestors(organization.getAncestors());
                    nonOpenInfo.setCreateBy(param.getUserId())
                            .setCreateDate(LocalDateTime.now())
                            .setUpdateBy(param.getUserId())
                            .setUpdateDate(LocalDateTime.now());
                    return nonOpenInfo;
                }).collect(Collectors.toList());
        ProgressManager.updateProgressCurrent(40, "装配考生数据完成");

        // 插入
        List<List<NonOpenInfo>> partitionNonOpenInfoList = Lists.partition(openInfoList, NumberEnum.TEN.getK());
        for (int i = 0; i < partitionNonOpenInfoList.size(); i++) {
            List<NonOpenInfo> nonOpenInfos = CollUtil.get(partitionNonOpenInfoList, i);
            this.saveBatch(nonOpenInfos);
            ProgressManager.stepProgressCurrent(i, partitionNonOpenInfoList.size(), 40, 80, "第" + (i + 1) + "批数据入库完成，总共" + partitionNonOpenInfoList.size() + "批数据");
        }
        ProgressManager.finish("入库完成");
    }

    private String getCardType(String stringCellValue) {
        if ("大陆居民身份证".equals(stringCellValue)) {
            return "1";
        }
        if ("港澳台身份证".equals(stringCellValue)) {
            return "0";
        }
        if ("护照".equals(stringCellValue)) {
            return "2";
        }
        return "1";
    }

    @Override
    public int queryByCard(NonOpenInfoSaveParam nonOpenInfo) {
        return nonOpenMapper.getStudentByCard(nonOpenInfo.getProjectId(), nonOpenInfo.getCardNumber(), nonOpenInfo.getId());
    }

    @Override
    public void updateBm(String studentId, String projectId) {
        String cardNumString = nonOpenMapper.queryStuCardNum(studentId, projectId);
        nonOpenMapper.updateBm(studentId, projectId, cardNumString);

    }

    @Override
    public NonOpenInfo queryStudentByCardNum(String projectId, String cardNum) {
        return nonOpenMapper.queryStudentByCardNum(projectId, cardNum);
    }

    @Override
    public void saveOrUpdateStudent(NonOpenInfoSaveParam param) {
        Project project = iProjectService.getProjectById(param.getProjectId());

        NonOpenInfo nonOpenInfo = null;
        if (StrUtil.isNotBlank(param.getId())) {
            nonOpenInfo = getById(param.getId());
            if (nonOpenInfo != null) {
                if (nonOpenInfo.getIsBm()) {
                    BusinessLogicException.throwException(31211208, "已报名考生不允许修改信息", nonOpenInfo);
                }
            }
        }

        // 判断报名资格，证件号码应该限制为唯一性 
        // 校验证件类型转换
        IdCardTypeEnum idCardTypeEnum = IdCardTypeEnum.getByKeyword(param.getCardType());
        if (ObjectUtil.equals(idCardTypeEnum, IdCardTypeEnum.NONE)) {
            BusinessLogicException.throwException(31213105, "不支持的证件类型");
        }
        // 大陆身份证验证
        if (ObjectUtil.equals(idCardTypeEnum, IdCardTypeEnum.CHINA)) {
            boolean validCard = IdcardUtil.isValidCard(param.getCardNumber());
            if (!validCard) {
                BusinessLogicException.throwException(31211209, "大陆居民身份证验证不合法", param);
            }
            // 港澳台身份证验证
        } else if (ObjectUtil.equals(idCardTypeEnum, IdCardTypeEnum.GAT)) {
            if (!param.getCardNumber().matches(Constant.HKMACAOTAWAN)) {
                BusinessLogicException.throwException(31211210, "港澳台身份证验证不合法", param);
            }
        }

        int count = this.queryByCard(param);
        if (count > 0) {
            BusinessLogicException.throwException(31211203, "身份证号码已存在", param);
        }

        List<String> subjectIds = StrUtil.splitTrim(param.getSubjectIds(), StrPool.COMMA);
        if (CollUtil.isNotEmpty(subjectIds)) {
            for (String subjectId : subjectIds) {
                ProjectSubject projectSubject = iProjectSubjectService.queryBySubjectId(param.getProjectId(), subjectId);
                if (projectSubject == null) {
                    BusinessLogicException.throwException(31211206, "报名科目不存在", subjectId);
                }
            }
        }

        NonOpenInfo nonOpenInfoSave = BeanUtil.toBean(param, NonOpenInfo.class);
        // 如果为新增，则用当前报名信息中的机构信息
        if (nonOpenInfo == null) {
            nonOpenInfoSave.setDeptId(project.getDeptId());
            nonOpenInfoSave.setOfficeId(project.getOfficeId());
        }
        String userId = iLoginService.getUserId();
        nonOpenInfoSave.setSubjectIds(CollUtil.join(subjectIds, StrPool.COMMA))
                .setCreateBy(userId)
                .setCreateDate(LocalDateTime.now())
                .setUpdateBy(userId)
                .setUpdateDate(LocalDateTime.now());

        boolean i = this.saveOrUpdate(nonOpenInfoSave);
        if (!i) {
            BusinessLogicException.throwException(31211204, "保存失败请重试");
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void batchDeleteStudent(String studentId) {
        if (KqStringUtils.isBlank(studentId)) {
            BusinessLogicException.throwException(Constant.RESPONSE_CODE_31010124, "未选择任何考生");
        }
        List<String> studentIds = StrUtil.split(studentId, StrPool.COMMA);
        for (String id : studentIds) {
            this.deleteStudent(id);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void batchSelectStudentSave(BatchSelectStudentSaveParam batchSelectStudentSaveParam) {
        Project project = iProjectService.getProjectById(batchSelectStudentSaveParam.getProjectId());
        ProgressManager.updateProgressCurrent(20, "获取报名信息完成");

        for (int i = 0; i < batchSelectStudentSaveParam.getStudentQualifications().size(); i++) {
            BatchSelectStudentDataParam studentQualification = CollUtil.get(batchSelectStudentSaveParam.getStudentQualifications(), i);
            boolean nonExistIdCard = this.filterNonExistIdCard(studentQualification.getCardNumber(), batchSelectStudentSaveParam.getProjectId());
            boolean nonExistStudentId = this.filterNonExistStudentId(studentQualification.getStudentBaseId(), batchSelectStudentSaveParam.getProjectId());
            if (!nonExistIdCard || !nonExistStudentId) {
                BusinessLogicException.throwException(31212202, "考生信息已存在报名库中", studentQualification);
            }

            String format = StrUtil.format("{}考生数据校验通过", studentQualification.getStudentName());
            ProgressManager.stepProgressCurrent(i, batchSelectStudentSaveParam.getStudentQualifications().size(), 30, 50, format);
        }

        List<NonOpenInfo> nonOpenInfos = batchSelectStudentSaveParam.getStudentQualifications()
                .stream()
                .map(data -> this.assembleNonOpenEntity(data, batchSelectStudentSaveParam.getProjectId(), batchSelectStudentSaveParam.getUserId()))
                .collect(Collectors.toList());
        ProgressManager.updateProgressCurrent(60, "装配考生信息完成");

        // 插入
        List<List<NonOpenInfo>> partitionNonOpenInfoList = Lists.partition(nonOpenInfos, NumberEnum.TEN.getK());
        // 如果数据量比较大的话，分组修改10一组
        if (CollectionUtils.isNotEmpty(partitionNonOpenInfoList)) {
            for (int i = 0; i < partitionNonOpenInfoList.size(); i++) {
                List<NonOpenInfo> nonOpenInfoSaves = CollUtil.get(partitionNonOpenInfoList, i);
                this.saveBatch(nonOpenInfoSaves);

                String message = StrUtil.format("第{}批数据入库完成，总共{}批数据", i + 1, partitionNonOpenInfoList.size());
                ProgressManager.stepProgressCurrent(i, batchSelectStudentSaveParam.getStudentQualifications().size(), 30, 90, message);
            }
        }
        ProgressManager.finish("入库完成");
    }

    @Override
    public BaseCheckEntity batchSelectStudentCheck(BatchSelectStudentSaveParam batchSelectStudentSaveParam) {
        List<BatchSelectStudentDataParam> studentQualifications = batchSelectStudentSaveParam.getStudentQualifications();
        if (CollUtil.isEmpty(studentQualifications) && CollUtil.isEmpty(batchSelectStudentSaveParam.getDeptIds())) {
            BusinessLogicException.throwException(31213109, "请选择考生或机构部门", batchSelectStudentSaveParam);
        }

        if (CollUtil.isNotEmpty(batchSelectStudentSaveParam.getDeptIds())) {
            // 通过部门ID获取远程考生信息
            List<OrganCandidateInfoVO> organCandidateInfoList = getRemoteOrganStudentInfo(batchSelectStudentSaveParam.getDeptIds());
            ProgressManager.updateProgressCurrent(10, "获取部门考生信息完成");

            // 装配转换部门考生数据为导入考生数据
            List<BatchSelectStudentDataParam> studentDeptQualifications = assembleCaseDeptNonOpenEntity(organCandidateInfoList);
            ProgressManager.updateProgressCurrent(15, "装配转换部门考生数据为导入考生数据完成");

            // 合并考生数据
            if (CollUtil.isEmpty(studentQualifications)) {
                studentQualifications = studentDeptQualifications;
            } else {
                CollUtil.addAll(studentQualifications, studentDeptQualifications);
            }
            ProgressManager.updateProgressCurrent(18, "部门数据合并完成");
        }

        Project project = iProjectService.getProjectById(batchSelectStudentSaveParam.getProjectId());
        ProgressManager.updateProgressCurrent(20, "获取报名信息成功");

        // 校验考生信息
        List<BatchCheckStudentMessageVO> batchCheckStudentMessageVOS = this.checkSelectStudent(studentQualifications, batchSelectStudentSaveParam.getProjectId());
        ProgressManager.updateProgressCurrent(71, "校验考生信息完成");

        // Map<ValidStatus, List<BatchCheckStudentMessageVO>>
        Map<Boolean, List<BatchCheckStudentMessageVO>> validResult = batchCheckStudentMessageVOS.stream()
                .collect(Collectors.groupingBy(BatchCheckStudentMessageVO::getValidStatus));
        ProgressManager.updateProgressCurrent(80, "分组考生信息完成");

        // 验证通过的数据List<BatchSelectStudentDataParam>
        List<BatchSelectStudentDataParam> successStudentData = Opt.of(validResult)
                .map(data -> data.get(Boolean.FALSE))
                .stream()
                .flatMap(Collection::stream)
                .map(BatchCheckStudentMessageVO::getStudentSelectData)
                .collect(Collectors.toList());
        ProgressManager.updateProgressCurrent(90, "分离考生信息完成");

        List<BatchCheckStudentMessageVO> validSuccessResult = validResult.get(Boolean.FALSE);
        List<BatchCheckStudentMessageVO> validFailResult = validResult.get(Boolean.TRUE);

        // 获取列表头
        Map<String, String> successViewHead = TableCheckViewUtil.getViewHead(BatchCheckStudentMessageVO.class, SuccessMapping.class);
        ProgressManager.updateProgressCurrent(91, "获取成功视图头完成");

        Map<String, String> failViewHead = TableCheckViewUtil.getViewHead(BatchCheckStudentMessageVO.class, FailMapping.class);
        ProgressManager.updateProgressCurrent(92, "获取失败视图头完成");

        // 获取视图渲染数据
        List<Map<String, String>> successViewData = TableCheckViewUtil.getViewData(validSuccessResult, SuccessMapping.class);
        ProgressManager.updateProgressCurrent(93, "获取成功视图数据完成");

        List<Map<String, String>> failViewData = TableCheckViewUtil.getViewData(validFailResult, FailMapping.class);
        ProgressManager.updateProgressCurrent(94, "获取失败视图数据完成");

        return new BatchSelectStudentMessageVO()
                .setStudentData(successStudentData)
                .setValidSuccessResult(validSuccessResult)
                .setValidFailResult(validFailResult)
                .setSuccessViewHead(successViewHead)
                .setFailViewHead(failViewHead)
                .setSuccessViewData(successViewData)
                .setFailViewData(failViewData);
    }

    /**
     * 通过部门ID获取远程考生信息
     *
     * @param deptIds
     * @return
     */
    private List<OrganCandidateInfoVO> getRemoteOrganStudentInfo(Set<String> deptIds) {
        PtCandidateParamVO ptCandidateParam = new PtCandidateParamVO();
        ptCandidateParam.setStatus("1");
        ptCandidateParam.setCurrent(1);
        ptCandidateParam.setSize(1000000);
        ptCandidateParam.setDeptIds(deptIds);
        PageParam<OrganCandidateInfoVO> voPageParam = RemoteUtil.getOrThrows(() -> sqClient.getByPage(ptCandidateParam));
        List<OrganCandidateInfoVO> organCandidateInfoList = voPageParam.getRecords();
        if (CollUtil.isEmpty(organCandidateInfoList)) {
            BusinessLogicException.throwException(31213110, "未获取到部门考生信息", ptCandidateParam);
        }
        return organCandidateInfoList;
    }

    /**
     * 装配转换部门考生数据为导入考生数据
     *
     * @param organCandidateInfoList
     * @return
     */
    private List<BatchSelectStudentDataParam> assembleCaseDeptNonOpenEntity(List<OrganCandidateInfoVO> organCandidateInfoList) {
        List<BatchSelectStudentDataParam> studentDataParams = organCandidateInfoList.stream()
                .map(data -> {
                    BatchSelectStudentDataParam batchSelectStudentDataParam = BatchSelectStudentDataParam.builder()
                            .studentBaseId(data.getId())
                            .studentName(data.getName())
                            .cardNumber(data.getIdNumber())
                            .deptId(data.getDeptId())
                            .officeId(data.getOfficeId())
                            .build();
                    if (NumberUtil.isNumber(data.getIdType())) {
                        batchSelectStudentDataParam.setCardType(Integer.valueOf(data.getIdType()));
                    }
                    
                    return batchSelectStudentDataParam;
                })
                .collect(Collectors.toList());

        // 获取部门的ancestors
        List<String> orgIds = studentDataParams.stream()
                .map(BatchSelectStudentDataParam::getDeptId)
                .distinct()
                .collect(Collectors.toList());
        String responseJson = permissionLoginClient.getAncestorsNames(orgIds);
        TypeReference<ApiResponse<Map<String, String>>> responseTypeReference = new TypeReference<ApiResponse<Map<String, String>>>() {};
        ApiResponse<Map<String, String>> mapApiResponse = JSON.parseObject(responseJson, responseTypeReference);
        Map<String, String> deptMappingAncestors = RemoteUtil.getOrThrows(mapApiResponse);

        for (BatchSelectStudentDataParam batchSelectStudentDataParam : studentDataParams) {
            String deptAncestors = MapUtil.getStr(deptMappingAncestors, batchSelectStudentDataParam.getDeptId());
            batchSelectStudentDataParam.setOfficeName(deptAncestors);
        }
        return studentDataParams;
    }

    @Override
    public List<String> getStudentBaseIds(String projectId) {
        return this.lambdaQuery()
                .eq(BaseEntity::getDelFlag, Boolean.FALSE)
                .eq(NonOpenInfo::getProjectId, projectId)
                .list()
                .stream()
                .map(NonOpenInfo::getStudentBaseId)
                .filter(StrUtil::isNotBlank)
                .distinct()
                .collect(Collectors.toList());
    }

    @Override
    public BaseCheckEntity importStudentQualificationsCheck(ImportStudentQualificationsCheckParam importStudentQualificationsCheckParam) {
        Project project = iProjectService.getProjectById(importStudentQualificationsCheckParam.getProjectId());
        ProgressManager.updateProgressCurrent(20, "获取报名信息成功");

        // 解析导入考生报名资格文件数据
        List<ImportStudentQualificationsDTO> importStudentQualificationsDTOS = this.analysisImportStudentQualificationFile(importStudentQualificationsCheckParam.getImportFileUrl());
        ProgressManager.updateProgressCurrent(50, "解析导入考生报名资格文件数据完成");

        // 校验导入考生资格信息
        List<ImportStudentQualificationMessageVO> importStudentQualificationMessageVOS = this.checkImportStudentQualificationData(importStudentQualificationsDTOS, importStudentQualificationsCheckParam.getProjectId());
        ProgressManager.updateProgressCurrent(60, "校验导入考生资格信息完成");

        // Map<ValidStatus, List<ImportStudentQualificationMessageVO>>
        Map<Boolean, List<ImportStudentQualificationMessageVO>> validResult = importStudentQualificationMessageVOS.stream()
                .collect(Collectors.groupingBy(ImportStudentQualificationMessageVO::getValidStatus));
        ProgressManager.updateProgressCurrent(70, "分组导入考生资格信息完成");

        // 验证通过的数据List<ImportStudentQualificationVO>
        List<ImportStudentQualificationVO> successStudentData = Opt.of(validResult)
                .map(data -> data.get(Boolean.FALSE))
                .stream()
                .flatMap(Collection::stream)
                .map(ImportStudentQualificationMessageVO::getStudentQualification)
                .collect(Collectors.toList());
        ProgressManager.updateProgressCurrent(80, "分离导入考生资格信息完成");

        List<ImportStudentQualificationMessageVO> validSuccessResult = validResult.get(Boolean.FALSE);
        List<ImportStudentQualificationMessageVO> validFailResult = validResult.get(Boolean.TRUE);

        // 获取列表头
        Map<String, String> successViewHead = TableCheckViewUtil.getViewHead(ImportStudentQualificationMessageVO.class, SuccessMapping.class);
        ProgressManager.updateProgressCurrent(91, "获取成功视图头完成");

        Map<String, String> failViewHead = TableCheckViewUtil.getViewHead(ImportStudentQualificationMessageVO.class, FailMapping.class);
        ProgressManager.updateProgressCurrent(92, "获取失败视图头完成");

        // 获取视图渲染数据
        List<Map<String, String>> successViewData = TableCheckViewUtil.getViewData(validSuccessResult, SuccessMapping.class);
        ProgressManager.updateProgressCurrent(93, "获取成功视图数据完成");

        List<Map<String, String>> failViewData = TableCheckViewUtil.getViewData(validFailResult, FailMapping.class);
        ProgressManager.updateProgressCurrent(94, "获取失败视图数据完成");

        return new ImportStudentQualificationsCheckVO()
                .setStudentData(successStudentData)
                .setValidSuccessResult(validSuccessResult)
                .setValidFailResult(validFailResult)
                .setSuccessViewHead(successViewHead)
                .setFailViewHead(failViewHead)
                .setSuccessViewData(successViewData)
                .setFailViewData(failViewData);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void batchSubjectSetting(BatchSubjectSettingParam param) {
        Project project = iProjectService.getProjectById(param.getProjectId());

        List<ProjectSubject> projectSubjects = iProjectSubjectService.getProjectSubjectsByProjectId(param.getProjectId());
        Set<String> subjectIds = projectSubjects.stream()
                .map(ProjectSubject::getSubjectId)
                .collect(Collectors.toSet());

        for (String subjectId : param.getSubjectIds()) {
            if (!CollUtil.contains(subjectIds, subjectId)) {
                BusinessLogicException.throwException(31214102, "报名中不存在选择的科目", subjectId);
            }
        }

        String subjectIdsText = CollUtil.join(param.getSubjectIds(), StrPool.COMMA);

        for (String qualificationId : param.getQualificationIds()) {
            NonOpenInfo nonOpenInfo = getById(qualificationId);
            if (nonOpenInfo != null) {
                if (nonOpenInfo.getIsBm()) {
                    BusinessLogicException.throwException(31214103, "已报名考生不允许修改科目", nonOpenInfo);
                }
            }

            this.lambdaUpdate()
                    .eq(BaseEntity::getId, qualificationId)
                    .set(NonOpenInfo::getSubjectIds, subjectIdsText)
                    .set(BaseEntity::getUpdateBy, iLoginService.getUserId())
                    .set(BaseEntity::getUpdateDate, LocalDateTime.now())
                    .update();
        }
    }

    @Override
    public Boolean checkStudentHasQualifications(String projectId, List<StudentInfo> studentInfos, List<String> subjectIds) {
        List<NonOpenInfo> openInfoList = getStudentQualificationByIdcard(projectId, studentInfos);
        // 获取证件号码对应的科目ID集合
        List<String> nonOpenSubjectIds = openInfoList.stream()
                // 获取科目ID集合
                .map(NonOpenInfo::getSubjectIds)
                .map(data -> StrUtil.split(data, StrPool.COMMA))
                .filter(CollUtil::isNotEmpty)
                .flatMap(Collection::stream)
                .filter(StrUtil::isNotBlank)
                .distinct()
                .collect(Collectors.toList());
        if (CollUtil.isNotEmpty(nonOpenSubjectIds)) {
            boolean containsedAll = CollUtil.containsAll(nonOpenSubjectIds, subjectIds);
            if (!containsedAll) {
                BusinessLogicException.throwException(31111604, "存在没有资格报考的科目", "当前资格：{}，当前提交：{}", nonOpenSubjectIds, subjectIds);
            }
            return containsedAll;
        }
        return Boolean.TRUE;
    }

    @Override
    public List<NonOpenInfo> getStudentQualificationByIdcard(String projectId, List<StudentInfo> studentInfos) {
        // 获取证件号的采集字段
        String idCardCollectId = projectMapper.getCollectIdByProject(ComponentNameEnum.idcard.getCode(), projectId, DelFlagEnum.NORMAL.getCode());
        // 获取考生的证件号码
        List<String> idCards = studentInfos.stream()
                // 获取证件号的报名采集对应的考生信息
                .filter(data -> StrUtil.equals(data.getProjectCollectInfoId(), idCardCollectId))
                .map(StudentInfo::getValue)
                .filter(StrUtil::isNotBlank)
                .distinct()
                .collect(Collectors.toList());
        List<NonOpenInfo> openInfoList = idCards.stream()
                // 获取报名资格的对应证件号信息
                .map(idCard -> nonOpenMapper.queryStudentByCardNum(projectId, idCard))
                .filter(ObjectUtil::isNotEmpty)
                .collect(Collectors.toList());
        return openInfoList;
    }

    /**
     * 解析导入考生报名资格文件数据
     *
     * @param importFileUrl
     * @return
     */
    private List<ImportStudentQualificationsDTO> analysisImportStudentQualificationFile(String importFileUrl) {
        String fileSuffix = FileUtil.getSuffix(importFileUrl);
        if (!StrUtil.equalsAny(fileSuffix, EXCEL_FILE_TYPE)) {
            BusinessLogicException.throwException(31213102, "上传的文件类型不匹配", importFileUrl);
        }
        File uploadFile = FileUtil.createTempFile(StrPool.DOT + fileSuffix, Boolean.TRUE);
        long fileSize = HttpUtil.downloadFile(importFileUrl, uploadFile);
        if (fileSize == CommonConstant.ZERO) {
            BusinessLogicException.throwException(31213103, "请勿上传一个空文件", importFileUrl);
        }

        List<ImportStudentQualificationsDTO> importStudentQualificationsDTOS = EasyExcel.read(uploadFile)
                .head(ImportStudentQualificationsDTO.class)
                .sheet()
                .doReadSync();
        if (CollUtil.isEmpty(importStudentQualificationsDTOS)) {
            BusinessLogicException.throwException(31213104, "未解析出考生数据", importFileUrl);
        }

        return importStudentQualificationsDTOS;
    }

    /**
     * 校验导入考生报名资格数据
     *
     * @param importStudentQualificationsDTOS 导入考生报名数据
     * @param projectId                       报名ID
     * @return
     */
    @SuppressWarnings("all")
    private List<ImportStudentQualificationMessageVO> checkImportStudentQualificationData(List<ImportStudentQualificationsDTO> importStudentQualificationsDTOS, String projectId) {
        Map<String, Long> cardNumberCount = importStudentQualificationsDTOS.stream()
                .filter(data -> StrUtil.isNotBlank(data.getCardNumber()))
                .collect(Collectors.groupingBy(ImportStudentQualificationsDTO::getCardNumber, Collectors.counting()));

        List<ImportStudentQualificationMessageVO> importStudentQualificationMessageVOS = importStudentQualificationsDTOS.stream()
                .map(studentQualification -> {
                    ImportStudentQualificationVO studentQualificationVO = BeanUtil.toBean(studentQualification, ImportStudentQualificationVO.class);

                    CheckUtil.CheckHandler checkHandler = CheckUtil.none();
                    checkHandler.collect(studentQualification.getStudentName(), "考生姓名", StrRuler.notBlank());

                    // 校验证件号码
                    if (StrUtil.isBlank(studentQualification.getCardNumber())) {
                        checkHandler.collect(studentQualification.getCardNumber(), "证件号码", StrRuler.notBlank());
                    } else {
                        // 证件是否重复
                        checkHandler.collect(studentQualification.getCardNumber(), checkTarget -> {
                            Long numberCount = MapUtil.get(cardNumberCount, checkTarget, Long.class);
                            if (numberCount > CommonConstant.ONE) {
                                throw new CheckException(31213108, "证件号码重复");
                            }
                        });

                        // 是否已入库存在
                        boolean nonExistIdCard = filterNonExistIdCard(studentQualification.getCardNumber(), projectId);
                        checkHandler.collect(nonExistIdCard, checkTarget -> {
                            if (!checkTarget) {
                                throw new CheckException(31212102, "考生信息已存在当前报名中");
                            }
                        });
                    }

                    // 校验证件类型
                    if (StrUtil.isBlank(studentQualification.getCardType())) {
                        checkHandler.collect(studentQualification.getCardType(), "证件类型", StrRuler.notBlank(), IdCardRuler.typeCheck());
                    } else {
                        // 校验证件类型转换
                        IdCardTypeEnum idCardTypeEnum = IdCardTypeEnum.getByKeyword(studentQualification.getCardType());
                        checkHandler.collect(studentQualification.getCardType(), checkTarget -> {
                            if (ObjectUtil.equals(idCardTypeEnum, IdCardTypeEnum.NONE)) {
                                throw new CheckException(31213105, "不支持的证件类型");
                            }
                        });

                        // 只校验大陆居民身份证号码的有效性
                        if (ObjectUtil.equals(IdCardTypeEnum.CHINA, idCardTypeEnum)) {
                            // 校验证件有效性
                            checkHandler.collect(studentQualification.getCardNumber(), checkTarget -> {
                                if (IdcardUtil.isValidCard(checkTarget)) {
                                    return;
                                }
                                throw new CheckException(31213106, "证件号码校验不通过");
                            });
                        }

                        studentQualificationVO.setCardType(idCardTypeEnum.getCode().toString());
                    }

                    // 校验科目
                    if (StrUtil.isBlank(studentQualification.getSubjectNames())) {
                        checkHandler.collect(studentQualification.getSubjectNames(), "科目", StrRuler.notBlank());
                    } else {
                        // 科目是否不完整
                        List<String> subjectNameList = StrUtil.split(studentQualification.getSubjectNames(), DOT);
                        List<String> subjectIds = subjectNameList.stream()
                                .map(subjectName -> iProjectSubjectService.querySubjectByName(projectId, subjectName))
                                .filter(ObjectUtil::isNotEmpty)
                                .map(ProjectSubject::getSubjectId)
                                .filter(StrUtil::isNotBlank)
                                .collect(Collectors.toList());
                        checkHandler.collect(studentQualification.getSubjectNames(), checkTarget -> {
                            if (CollUtil.isEmpty(subjectNameList) || CollUtil.size(subjectNameList) != CollUtil.size(subjectIds)) {
                                throw new CheckException(31213107, "科目不存在或为空");
                            }
                        });

                        String subjectIdsText = CollUtil.join(subjectIds, StrPool.COMMA);
                        studentQualificationVO.setSubjectIds(subjectIdsText);
                    }

                    ImportStudentQualificationMessageVO importStudentQualificationMessageVO = BeanUtil.toBean(studentQualification, ImportStudentQualificationMessageVO.class);
                    importStudentQualificationMessageVO.setValidStatus(checkHandler.hasException())
                            .setValidMessage(checkHandler.allMessageCollect())
                            .setErrorCodes(checkHandler.allExceptionCodeCollect())
                            .setStudentQualification(studentQualificationVO);
                    return importStudentQualificationMessageVO;
                }).collect(Collectors.toList());
        return importStudentQualificationMessageVOS;
    }

    /**
     * 过滤科目不完整的
     * true 不完整
     * false 完整
     *
     * @param subjectNames
     * @param projectId
     * @return
     */
    private boolean filterNonExistSubject(String subjectNames, String projectId) {
        List<String> subjectNameList = StrUtil.split(subjectNames, DOT);
        if (CollUtil.isEmpty(subjectNameList)) {
            return Boolean.TRUE;
        }

        List<String> subjectIds = subjectNameList.stream()
                .map(subjectName -> iProjectSubjectService.querySubjectByName(projectId, subjectName))
                .map(ProjectSubject::getSubjectId)
                .collect(Collectors.toList());
        return CollUtil.size(subjectNameList) != CollUtil.size(subjectIds);
    }

    /**
     * 校验考生报名资格基础数据
     *
     * @param studentData
     * @return
     */
    @SuppressWarnings("all")
    private CheckUtil.CheckHandler checkStudentQualificationBase(ImportStudentQualificationsDTO studentData) {
        CheckUtil.CheckHandler checkHandler = CheckUtil.none();

        checkHandler.collect(studentData.getStudentName(), "考生姓名", StrRuler.notBlank())
                .collect(studentData.getCardType(), "证件类型", StrRuler.notBlank(), IdCardRuler.typeCheck())
                .collect(studentData.getCardNumber(), "证件号码", StrRuler.notBlank())
                .collect(studentData.getSubjectNames(), "科目", StrRuler.notBlank());

        return checkHandler;
    }

    /**
     * 校验考生信息
     *
     * @param studentQualifications 考生资格信息
     * @param projectId             报名ID
     * @return
     */
    @SuppressWarnings("all")
    private List<BatchCheckStudentMessageVO> checkSelectStudent(List<BatchSelectStudentDataParam> studentQualifications, String projectId) {
        Map<String, Long> cardNumberCount = studentQualifications.stream()
                .filter(data -> StrUtil.isNotBlank(data.getCardNumber()))
                .collect(Collectors.groupingBy(BatchSelectStudentDataParam::getCardNumber, Collectors.counting()));

        List<BatchCheckStudentMessageVO> batchCheckStudentMessageVOS = Lists.newLinkedList();
        for (int i = 0; i < studentQualifications.size(); i++) {
            BatchSelectStudentDataParam studentData = CollUtil.get(studentQualifications, i);
            CheckUtil.CheckHandler checkHandler = this.checkStudentBase(studentData);

            // 校验证件号码
            if (StrUtil.isBlank(studentData.getCardNumber())) {
                checkHandler.collect(studentData.getCardNumber(), "证件号码", StrRuler.notBlank());
            } else {
                // 证件是否重复
                checkHandler.collect(studentData.getCardNumber(), checkTarget -> {
                    Long numberCount = MapUtil.get(cardNumberCount, checkTarget, Long.class);
                    if (numberCount > CommonConstant.ONE) {
                        throw new CheckException(31213108, "证件号码重复");
                    }
                });
                // 是否已入库存在
                boolean nonExistIdCard = filterNonExistIdCard(studentData.getCardNumber(), projectId);
                checkHandler.collect(nonExistIdCard, checkTarget -> {
                    if (!checkTarget) {
                        throw new CheckException(31212102, "考生信息已存在当前报名中");
                    }
                });
            }
            // 是否已入库考生平台ID
            boolean nonExistStudentId = this.filterNonExistStudentId(studentData.getStudentBaseId(), projectId);
            checkHandler.collect(nonExistStudentId, checkTarget -> {
                if (!checkTarget) {
                    throw new CheckException(31212103, "考生信息已绑定当前报名中");
                }
            });

            BatchCheckStudentMessageVO batchCheckStudentMessageVO = BeanUtil.toBean(studentData, BatchCheckStudentMessageVO.class);
            batchCheckStudentMessageVO.setValidStatus(checkHandler.hasException())
                    .setValidMessage(checkHandler.allMessageCollect())
                    .setErrorCodes(checkHandler.allExceptionCodeCollect())
                    .setStudentSelectData(studentData);
            batchCheckStudentMessageVOS.add(batchCheckStudentMessageVO);

            String format = StrUtil.format("校验{}考生数据完成", studentData.getStudentName());
            ProgressManager.stepProgressCurrent(i, studentQualifications.size(), 50, 70, format);
        }
        return batchCheckStudentMessageVOS;
    }

    /**
     * 检查考生基础数据
     *
     * @param studentData
     * @return
     */
    @SuppressWarnings("all")
    private CheckUtil.CheckHandler checkStudentBase(BatchSelectStudentDataParam studentData) {
        CheckUtil.CheckHandler checkHandler = CheckUtil.none();

        checkHandler.collect(studentData.getStudentName(), "考生姓名", StrRuler.notBlank())
                .collect(studentData.getCardType(), "证件类型", IntRuler.notNull())
                .collect(studentData.getOfficeId(), "所属机构", StrRuler.notBlank())
                .collect(studentData.getOfficeName(), "所属机构名称", StrRuler.notBlank())
                .collect(studentData.getDeptId(), "所属部门", StrRuler.notBlank());

        return checkHandler;
    }

    /**
     * 不存在证件号码的通过，存在证件号码的不通过
     *
     * @param idCardNo 证件号码
     * @return
     */
    private boolean filterNonExistIdCard(String idCardNo, String projectId) {
        List<NonOpenInfo> nonOpenInfos = this.lambdaQuery()
                .eq(BaseEntity::getDelFlag, Boolean.FALSE)
                .eq(NonOpenInfo::getProjectId, projectId)
                .eq(NonOpenInfo::getCardNumber, idCardNo)
                .list();
        return CollUtil.isEmpty(nonOpenInfos);
    }

    /**
     * 过滤不存在考生ID
     *
     * @param studentId
     * @param projectId
     * @return
     */
    private boolean filterNonExistStudentId(String studentId, String projectId) {
        if (StrUtil.isBlank(studentId)) {
            return Boolean.TRUE;
        }

        Integer count = this.lambdaQuery()
                .eq(BaseEntity::getDelFlag, Boolean.FALSE)
                .eq(NonOpenInfo::getProjectId, projectId)
                .eq(NonOpenInfo::getStudentBaseId, studentId)
                .count();
        return count <= CommonConstant.ZERO;
    }

    /**
     * 装配考生报名资格
     *
     * @param studentData
     * @return
     */
    private NonOpenInfo assembleNonOpenEntity(BatchSelectStudentDataParam studentData, String projectId, String userId) {
        NonOpenInfo nonOpenInfo = BeanUtil.toBean(studentData, NonOpenInfo.class);
        nonOpenInfo.setId(UUIDUtils.newSortUUID());
        nonOpenInfo.setProjectId(projectId);
        nonOpenInfo.setCreateDate(LocalDateTime.now());
        nonOpenInfo.setCreateBy(userId);
        nonOpenInfo.setUpdateDate(LocalDateTime.now());
        nonOpenInfo.setUpdateBy(userId);
        return nonOpenInfo;
    }
}
